/**
 *
 */
package com.vivi.codereview.tools;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.vivi.codereview.WhatTheFuckException;

/**
 * @author nvillard
 *
 */
public class ClearCaseHandler {

  private SystemHandler sys;

  public ClearCaseHandler() {
    this.sys = new SystemHandler();
  }

  /**
   * Command we process to get all checkouts done by the user (me) in the
   * current stream.
   */
  protected static final String CC_FIND_ALL_CHECKOUT_COMMAND = "cleartool lscheckout -cview -me -avobs";

  /**
   * We look for PTR number in an activity by looking for 7 or 8 consecutive
   * numbers.
   */
  protected static final Pattern CC_ACTIVITY_REGEXP = Pattern.compile("(\\d{7,8})");

  /**
   * In CC_ACTIVITY_REGEXP, we want to consider group between bracets at this
   * index.
   */
  protected static final int CC_ACTIVITY_GROUP = 1;

  /**
   * We look for a file in ClearCase output by looking for a String, between
   * double-quotes, and finishing with one of the given extensions.
   */
  protected static final Pattern CC_FILE_REGEXP = Pattern.compile("(\"(" + SystemHandler.FILE_REGEXP + ")\")");

  /**
   * In CC_FILE_REGEXP, we want to consider group between bracets at this
   * index.
   */
  protected static final int CC_FILE_GROUP = 2;

  protected static final String CC_CHECK_IS_CHECKOUTED = "cleartool lscheckout -recurse -short -cview {0}";

  protected static final String CC_DIFF = "cleartool diff -g {0} {1}";

  protected static final String CC_UPDATE = "cleartool update -overwrite -f {0}";

  protected static final String CC_DESC = "cleartool descr -l {0}";

  protected static final Pattern CC_EXTRACT_PNAME = Pattern.compile("version \"(.+)\"");

  protected static final int CC_GET_PNAME_GROUP = 1;

  protected static final String CC_UPDATE_OK = "1 objects, copied";

  /**
   * We read each group of 3 lines to get the file (in first line) and the
   * activity (3rd line). The second one is useless. We need to ensure it is
   * the case for folders, creation, deletion as well.
   *
   * @param files
   * @return
   * @throws IOException
   */
  public Map<String, Set<String>> sortCheckoutsPerPtr(List<String> checkoutOutput) throws Exception {
    Map<String, Set<String>> filesPtrMap = new HashMap<String, Set<String>>();
    // We regroup each checkout together, grouping them 3 by 3
    List<List<String>> entries = new ArrayList<List<String>>();
    for (int i = 0; i < checkoutOutput.size() - 2; i += 3) {
      // At least for a "classic" checkouts reported, we have 3 lines
      // related to.
      // The second line is useless in our case.
      List<String> entry = new ArrayList<String>();
      entry.add(checkoutOutput.get(i));
      entry.add(checkoutOutput.get(i + 2));
      Log.log("we group " + entry, Log.D);
      entries.add(entry);
    }
    // For each entry, we will extract the activity and store the full line
    // with file into Map
    for (List<String> entry : entries) {
      sortOneCheckoutedFile(filesPtrMap, entry.get(0), entry.get(1));
    }
    return filesPtrMap;
  }

  public List<String> getCheckoutedFiles(String root) throws Exception {
    return sys.launchCommandAndGetOutput(root, CC_FIND_ALL_CHECKOUT_COMMAND);
  }

  /**
   * Adding a PTR-Files entry in the Map of all checkouted files. To do that,
   * we use regexp to find the filename, and the ptr number mandatory in the
   * activity
   *
   * @param map
   * @param stringWithFile
   * @param stringWithActivity
   */
  protected void sortOneCheckoutedFile(Map<String, Set<String>> map, String stringWithFile, String stringWithActivity)
      throws Exception {
    // We first look for the filename
    String filename = extractFilename(stringWithFile);
    if (filename != null) {
      String ptrNumber = extractPtrNumber(stringWithActivity);
      if (ptrNumber != null) {
        addFileForPtr(map, ptrNumber, filename);
      }
      else {
        Log.log("Could not find PTR number for " + stringWithActivity, Log.W);
      }
    }
    else {
      Log.log("Could not find file for " + stringWithFile, Log.W);
    }
  }

  /**
   * Extract the PTR number from a ClearCase output line related to the
   * activity
   *
   * @param string
   * @return
   */
  protected String extractPtrNumber(String string) throws Exception {
    String ptrNumber = null;
    Matcher matcher = CC_ACTIVITY_REGEXP.matcher(string);
    if (matcher.find()) {
      // We extract the ptr number
      ptrNumber = matcher.group(CC_ACTIVITY_GROUP);
      Log.log("From " + string + " we extract this PTR number " + ptrNumber, Log.D);
    }
    return ptrNumber;
  }

  /**
   * Extract the filename from a ClearCase output line related to a file
   *
   * @param string
   * @return
   */
  protected String extractFilename(String string) throws Exception {
    String filename = null;
    Matcher matcher = CC_FILE_REGEXP.matcher(string);
    if (matcher.find()) {
      // We extract the ptr number
      filename = matcher.group(CC_FILE_GROUP);
      Log.log("From " + string + " we extract this filename " + filename, Log.D);
    }
    return filename;
  }

  /**
   * Update Map of PTR and related files with new entry
   *
   * @param map
   * @param ptr
   * @param file
   */
  protected void addFileForPtr(Map<String, Set<String>> map, String ptr, String file) throws Exception {
    Set<String> files;
    if (!map.containsKey(ptr)) {
      files = new TreeSet<String>();
    }
    else {
      files = map.get(ptr);
    }
    files.add(file);
    map.put(ptr, files);
  }

  /**
   * Get pname for a given file.
   *
   * @param root
   * @param filename
   * @return
 * @throws Exception
 * @throws IOException
   */
  public String getPname(String root, String filename) throws Exception {
    File file = sys.getFile(root, filename);
    if (!file.exists()) {
    	Log.log("This file doesn't exist in your local view "+filename, Log.W);
    	return null;
    }
    else if (sys.isReadOnly(file)) {
      return getPNameForReadOnlyFile(root, filename);
    }
    else {
      Log.log("this file is not ReadOnly " + file, Log.E);
      throw new WhatTheFuckException(Log.Exit.FILE_INVALID_STATE);
    }
  }

  private String getPNameForReadOnlyFile(String root, String file) throws Exception {
    String command = Utils.formatString(CC_DESC, file);
    List<String> descriptions = sys.launchCommandAndGetOutput(root, command);
    if (descriptions != null && descriptions.size() > 0) {
      String desc = descriptions.get(0);
      Matcher matcher = CC_EXTRACT_PNAME.matcher(desc);
      if (matcher.find()) {
        String pname = matcher.group(CC_GET_PNAME_GROUP);
        Log.log("Pname:\t" + file + "\t" + pname, Log.D);
        return pname;
      }
      else {
        Log.log("Not able to find pname for this file " + file, Log.W);
        return null;
      }
    }
    else {
      Log.log("Not able to get description for this file " + file, Log.W);
      return null;
    }
  }

  /**
   * Check if a given file is checkouted.
   *
   * @param root
   * @param file
   * @return
   * @throws IOException
   */
  public boolean isCheckouted(String root, String file) throws Exception {
    String command = Utils.formatString(CC_CHECK_IS_CHECKOUTED, file);
    List<String> output = sys.launchCommandAndGetOutput(root, command);
    Log.log("Check if is checkouted for " + file + " returned: " + output.toString(), Log.D);
    return output != null && output.size() > 0;
  }

  public void diffFile(String root, String localFile, String remoteFile) throws Exception {
    String command = Utils.formatString(CC_DIFF, remoteFile, localFile);
    sys.launchCommand(root, command);
  }

  public void updateFile(String root, String file) throws Exception {
    String command = Utils.formatString(CC_UPDATE, file);
    List<String> output = sys.launchCommandAndGetOutput(root, command);
    Log.log("unhijack this file " + file + ": " + output, Log.D);
    File realFile = sys.getFile(root, file);
    if (!sys.isReadOnly(realFile)) {
      Log.log("Was not able to update this file: " + realFile, Log.W);
    }
    else {
      Log.log("This file has been updated " + realFile, Log.D);
    }
  }
}
